﻿using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;

namespace IOP.Extension.Convert
{
    /// <summary>
    /// BCD转换扩展
    /// </summary>
    public static class BCDConvertExtension
    {
        /// <summary>
        /// 8421掩码
        /// </summary>
        private const byte BYTE_MASK = 0b0000_1111;
        /// <summary>
        /// 余三码掩码
        /// </summary>
        private const byte EXCESS_THREE_CODE_MASK = 0b0011_0011;
        /// <summary>
        /// BCD码最大值
        /// </summary>
        private const byte CODE_MAX = 0b0000_1001;

        /// <summary>
        /// 从数组中获取8421BCD码并变更下标
        /// </summary>
        /// <param name="data">原数据</param>
        /// <param name="index">起始索引</param>
        /// <param name="length">长度</param>
        /// <param name="result">结果</param>
        /// <param name="isCompress">是否压缩</param>
        /// <param name="endian">字节序</param>
        /// <param name="decimalDigit">小数位数</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void Get8421BCDFromBytes(this ReadOnlySpan<byte> data, ref int index, int length, out double result, bool isCompress, Endian endian = Endian.BigEndian, int decimalDigit = 0)
        {
            result = 0;
            int i = 0;
            int little = decimalDigit;
            int big = 0;
            int end = index + length - 1;
            while(i < length)
            {
                byte local = 0;
                switch (endian)
                {
                    case Endian.BigEndian:
                        local = data[end - i];
                        break;
                    case Endian.LittleEndian:
                        local = data[index + i];
                        break;
                }
                AddBCDValueFromBytes(local, ref result, isCompress, ref big, ref little);
                i++;
            }
            index += length;
        }
        /// <summary>
        /// 从余三码中获取值并变更下标
        /// </summary>
        /// <param name="data">原数组</param>
        /// <param name="index">数组下标</param>
        /// <param name="length">长度</param>
        /// <param name="result">结果</param>
        /// <param name="isCompress">是否压缩</param>
        /// <param name="endian">字节序</param>
        /// <param name="decimalDigit">小数位数</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void GetExcessThreeCodeFromBytes(this ReadOnlySpan<byte> data, ref int index, int length, out float result, bool isCompress, Endian endian = Endian.BigEndian, int decimalDigit = 0)
        {
            result = 0;
            int i = 0;
            int little = decimalDigit;
            int big = 0;
            int end = index + length - 1;
            while (i < length)
            {
                byte local = GetExcessThreeCode(ref data, i, index, end, endian);
                AddBCDValueFromBytes(local, ref result, isCompress, ref big, ref little);
                i++;
            }
            index += length;
        }
        /// <summary>
        /// 从数组中获取余三码并变更下标
        /// </summary>
        /// <param name="data">原数据</param>
        /// <param name="index">起始索引</param>
        /// <param name="length">长度</param>
        /// <param name="result">结果</param>
        /// <param name="isCompress">是否压缩</param>
        /// <param name="endian">字节序</param>
        /// <param name="decimalDigit">小数位数</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void GetExcessThreeCodeFromBytes(this ReadOnlySpan<byte> data, ref int index, int length, out double result, bool isCompress, Endian endian = Endian.BigEndian, int decimalDigit = 0)
        {
            result = 0;
            int i = 0;
            int little = decimalDigit;
            int big = 0;
            int end = index + length - 1;
            while (i < length)
            {
                byte local = GetExcessThreeCode(ref data, i, index, end, endian);
                AddBCDValueFromBytes(local, ref result, isCompress, ref big, ref little);
                i++;
            }
            index += length;
        }
        /// <summary>
        /// 从余三码中获取值并变更下标
        /// </summary>
        /// <param name="data">原数组</param>
        /// <param name="index">数组下标</param>
        /// <param name="length">长度</param>
        /// <param name="result">结果</param>
        /// <param name="isCompress">是否压缩</param>
        /// <param name="endian">字节序</param>
        /// <param name="decimalDigit">小数位数</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void GetExcessThreeCodeFromBytes(this ReadOnlySpan<byte> data, ref int index, int length, out decimal result, bool isCompress, Endian endian = Endian.BigEndian, int decimalDigit = 0)
        {
            result = 0;
            int i = 0;
            int little = decimalDigit;
            int big = 0;
            int end = index + length - 1;
            while (i < length)
            {
                byte local = GetExcessThreeCode(ref data, i, index, end, endian);
                AddBCDValueFromBytes(local, ref result, isCompress, ref big, ref little);
                i++;
            }
            index += length;
        }
        /// <summary>
        /// 从余三码中获取值并变更下标
        /// </summary>
        /// <param name="data">原数组</param>
        /// <param name="index">数组下标</param>
        /// <param name="length">长度</param>
        /// <param name="result">结果</param>
        /// <param name="isCompress">是否压缩</param>
        /// <param name="endian"></param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void GetExcessThreeCodeFromBytes(this ReadOnlySpan<byte> data, ref int index, int length, out ushort result, bool isCompress, Endian endian = Endian.BigEndian)
        {
            result = 0;
            int i = 0;
            int end = index + length - 1;
            int big = 0;
            while(i < length)
            {
                byte local = GetExcessThreeCode(ref data, i, index, end, endian);
                AddBCDValueFromBytes(local, ref result, isCompress, ref big);
                i++;
            }
            index += length;
        }
        /// <summary>
        /// 从余三码中获取值并变更下标
        /// </summary>
        /// <param name="data">原数组</param>
        /// <param name="index">数组下标</param>
        /// <param name="length">长度</param>
        /// <param name="result">结果</param>
        /// <param name="isCompress">是否压缩</param>
        /// <param name="endian"></param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void GetExcessThreeCodeFromBytes(this ReadOnlySpan<byte> data, ref int index, int length, out uint result, bool isCompress, Endian endian = Endian.BigEndian)
        {
            result = 0;
            int i = 0;
            int end = index + length - 1;
            int big = 0;
            while (i < length)
            {
                byte local = GetExcessThreeCode(ref data, i, index, end, endian);
                AddBCDValueFromBytes(local, ref result, isCompress, ref big);
                i++;
            }
            index += length;
        }
        /// <summary>
        /// 从余三码中获取值并变更下标
        /// </summary>
        /// <param name="data">原数组</param>
        /// <param name="index">数组下标</param>
        /// <param name="length">长度</param>
        /// <param name="result">结果</param>
        /// <param name="isCompress">是否压缩</param>
        /// <param name="endian"></param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void GetExcessThreeCodeFromBytes(this ReadOnlySpan<byte> data, ref int index, int length, out ulong result, bool isCompress, Endian endian = Endian.BigEndian)
        {
            result = 0;
            int i = 0;
            int end = index + length - 1;
            int big = 0;
            while (i < length)
            {
                byte local = GetExcessThreeCode(ref data, i, index, end, endian);
                AddBCDValueFromBytes(local, ref result, isCompress, ref big);
                i++;
            }
            index += length;
        }
             
        /// <summary>
        /// 获取余三码的单个字节
        /// </summary>
        /// <param name="data">数据</param>
        /// <param name="index">当前下标</param>
        /// <param name="endian">字节序</param>
        /// <param name="start">起始字节</param>
        /// <param name="end">终止字节</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static byte GetExcessThreeCode(ref ReadOnlySpan<byte> data, int index, int start, int end, Endian endian = Endian.BigEndian)
        {
            byte local = 0;
            switch (endian)
            {
                case Endian.BigEndian:
                    local = data[end - index];
                    local -= EXCESS_THREE_CODE_MASK;
                    break;
                case Endian.LittleEndian:
                    local = data[start + index];
                    local -= EXCESS_THREE_CODE_MASK;
                    break;
            }
            return local;
        }
        /// <summary>
        /// 累加BCD值
        /// </summary>
        /// <param name="source"></param>
        /// <param name="result"></param>
        /// <param name="isCompress"></param>
        /// <param name="big"></param>
        /// <param name="little"></param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static void AddBCDValueFromBytes(byte source, ref double result, bool isCompress, ref int big, ref int little)
        {
            if (isCompress)
            {
                if (source == 0)
                {
                    if (little >= 2)
                    {
                        little -= 2;
                        return;
                    }
                    else if (little == 1)
                    {
                        little--;
                        big++;
                        return;
                    }
                    else
                    {
                        big += 2;
                        return;
                    }
                }
                byte first = (byte)(source & BYTE_MASK);
                byte second = (byte)(source >> 4);
                if (first > CODE_MAX || second > CODE_MAX) throw new Exception($"The byte value {source} is out of BCD MAX Value");
                if (little >= 2)
                {
                    result += first * (Math.Pow(10, -little));
                    little--;
                    result += second * (Math.Pow(10, -little));
                    little--;
                }
                else if(little == 1)
                {
                    result += first * (Math.Pow(10, -little));
                    little--;
                    result += second * (Math.Pow(10, big));
                    big++;
                }
                else
                {
                    result += first * (Math.Pow(10, big));
                    big++;
                    result += second * (Math.Pow(10, big));
                    big++;
                }
            }
            else
            {
                if (source > CODE_MAX) throw new Exception($"The byte value {source} is out of BCD MAX Value");
                if (little > 0)
                {
                    result += source * (Math.Pow(10, -little));
                    little--;
                }
                else
                {
                    result += source * (Math.Pow(10, big));
                    big++;
                }
            }
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static void AddBCDValueFromBytes(byte source, ref float result, bool isCompress, ref int big, ref int little)
        {
            if (isCompress)
            {
                if(source == 0)
                {
                    if (little >= 2)
                    {
                        little -= 2;
                        return;
                    }
                    else if(little == 1)
                    {
                        little--;
                        big++;
                        return;
                    }
                    else
                    {
                        big += 2;
                        return;
                    }
                }
                byte first = (byte)(source & BYTE_MASK);
                byte second = (byte)(source >> 4);
                if (first > CODE_MAX || second > CODE_MAX) throw new Exception($"The byte value {source} is out of BCD MAX Value");
                if (little >= 2)
                {
                    result += (float)(first * (Math.Pow(10, -little)));
                    little--;
                    result += (float)(second * (Math.Pow(10, -little)));
                    little--;
                }
                else if(little == 1)
                {
                    result += (float)(first * (Math.Pow(10, -little)));
                    little--;
                    result += (float)(second * (Math.Pow(10, big)));
                    big++;
                }
                else
                {
                    result += (float)(first * (Math.Pow(10, big)));
                    big++;
                    result += (float)(second * (Math.Pow(10, big)));
                    big++;
                }
            }
            else
            {
                if (source > CODE_MAX) throw new Exception($"The byte value {source} is out of BCD MAX Value");
                if (little > 0)
                {
                    result += (float)(source * (Math.Pow(10, -little)));
                    little--;
                }
                else
                {
                    result += (float)(source * (Math.Pow(10, big)));
                    big++;
                }
            }
        }
        /// <summary>
        /// 累加BCD值
        /// </summary>
        /// <param name="source"></param>
        /// <param name="result"></param>
        /// <param name="isCompress"></param>
        /// <param name="big"></param>
        /// <param name="little"></param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static void AddBCDValueFromBytes(byte source, ref decimal result, bool isCompress, ref int big, ref int little)
        {
            if (isCompress)
            {
                if (source == 0)
                {
                    if (little >= 2)
                    {
                        little -= 2;
                        return;
                    }
                    else if (little == 1)
                    {
                        little--;
                        big++;
                        return;
                    }
                    else
                    {
                        big += 2;
                        return;
                    }
                }
                byte first = (byte)(source & BYTE_MASK);
                byte second = (byte)(source >> 4);
                if (first > CODE_MAX || second > CODE_MAX) throw new Exception($"The byte value {source} is out of BCD MAX Value");
                if (little >= 2)
                {
                    result += (decimal)(first * (Math.Pow(10, -little)));
                    little--;
                    result += (decimal)(second * (Math.Pow(10, -little)));
                    little--;
                }
                else if(little == 1)
                {
                    result += (decimal)(first * (Math.Pow(10, -little)));
                    little--;
                    result += (decimal)(second * (Math.Pow(10, big)));
                    big++;
                }
                else
                {
                    result += (decimal)(first * (Math.Pow(10, big)));
                    big++;
                    result += (decimal)(second * (Math.Pow(10, big)));
                    big++;
                }
            }
            else
            {
                if (source > CODE_MAX) throw new Exception($"The byte value {source} is out of BCD MAX Value");
                if (little > 0)
                {
                    result += (decimal)(source * (Math.Pow(10, -little)));
                    little--;
                }
                else
                {
                    result += (decimal)(source * (Math.Pow(10, big)));
                    big++;
                }
            }
        }
        /// <summary>
        /// 累加BCD值
        /// </summary>
        /// <param name="source"></param>
        /// <param name="result"></param>
        /// <param name="isCompress"></param>
        /// <param name="big"></param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static void AddBCDValueFromBytes(byte source, ref ushort result, bool isCompress, ref int big)
        {
            if (source == 0) return;
            if (isCompress)
            {
                byte first = (byte)(source & BYTE_MASK);
                byte second = (byte)(source >> 4);
                if (first > CODE_MAX || second > CODE_MAX) throw new Exception($"The byte value {source} is out of BCD MAX Value");
                result += (ushort)(first * (Math.Pow(10, big)));
                big++;
                result += (ushort)(second * (Math.Pow(10, big)));
                big++;
            }
            else
            {
                if (source > CODE_MAX) throw new Exception($"The byte value {source} is out of BCD MAX Value");
                result += (ushort)(source * (Math.Pow(10, big)));
                big++;
            }
        }
        /// <summary>
        /// 累加BCD值
        /// </summary>
        /// <param name="source"></param>
        /// <param name="result"></param>
        /// <param name="isCompress"></param>
        /// <param name="big"></param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static void AddBCDValueFromBytes(byte source, ref uint result, bool isCompress, ref int big)
        {
            if (source == 0) return;
            if (isCompress)
            {
                byte first = (byte)(source & BYTE_MASK);
                byte second = (byte)(source >> 4);
                if (first > CODE_MAX || second > CODE_MAX) throw new Exception($"The byte value {source} is out of BCD MAX Value");
                result += (uint)(first * (Math.Pow(10, big)));
                big++;
                result += (uint)(second * (Math.Pow(10, big)));
                big++;
            }
            else
            {
                if (source > CODE_MAX) throw new Exception($"The byte value {source} is out of BCD MAX Value");
                result += (uint)(source * (Math.Pow(10, big)));
                big++;
            }
        }
        /// <summary>
        /// 累加BCD值
        /// </summary>
        /// <param name="source"></param>
        /// <param name="result"></param>
        /// <param name="isCompress"></param>
        /// <param name="big"></param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static void AddBCDValueFromBytes(byte source, ref ulong result, bool isCompress, ref int big)
        {
            if (source == 0) return;
            if (isCompress)
            {
                byte first = (byte)(source & BYTE_MASK);
                byte second = (byte)(source >> 4);
                if (first > CODE_MAX || second > CODE_MAX) throw new Exception($"The byte value {source} is out of BCD MAX Value");
                result += (ulong)(first * (Math.Pow(10, big)));
                big++;
                result += (ulong)(second * (Math.Pow(10, big)));
                big++;
            }
            else
            {
                if (source > CODE_MAX) throw new Exception($"The byte value {source} is out of BCD MAX Value");
                result += (ulong)(source * (Math.Pow(10, big)));
                big++;
            }
        }
    }
}
